home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1992 June: ROMin Holiday / ADC Developer CD (1992-06) (''ROMin Holiday'')_iso / Developer Connection - 06-1992.iso / Development Platforms / LISP Related / U. Mass AI & LISP Tools / MODULES / RULES / Rules.lisp < prev   
Encoding:
Text File  |  1990-06-24  |  43.6 KB  |  947 lines  |  [TEXT/CCL ]

  1. ; (c) Copyright 1990 by University of Massachusetts. All rights reserved.
  2. ; This software was conceived, designed, and written by Dan Suthers 
  3. ; while supported by the National Science Foundation under grant number
  4. ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
  5. ; CA.  Partial support was also received from the Office of Naval Research
  6. ; under a University Research Initiative Grant, contract N00014-86-K-0764.
  7. ; Mr. Suthers created this software under his own initiative while in an 
  8. ; academic relationship with the University of Massachusetts.  The above
  9. ; copyright notice was a condition placed by University lawyers on approval
  10. ; of distribution of this software by Apple Computer, and is not meant to
  11. ; imply that this software was created in an employment or "work for hire"
  12. ; relationship between the University and Mr. Suthers.
  13. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  14. ; File:         RULES.lisp
  15. ; Author:       Dan Suthers
  16. ; Created:      19-Oct-88 21:57:32
  17. ; Modified:     22-Jun-90 02:16:28 (Dan Suthers)
  18. ; Language:     Common Lisp
  19. ; Package:      RULE
  20. ;
  21. ; Description:  Loads a rule-based reasoner built on the pattern matching 
  22. ;               facilities of DNET.  Supports forward and backward reasoning.
  23. ;
  24. ;               This is just a top-level REQUIRE file for the entire rules
  25. ;               system.  You may load subportions as noted below.  This file
  26. ;               also contains user and programmer documentation.
  27. ;
  28. ; (c) Copyright 1988, by Daniel D. Suthers
  29. ;                        Department of Computer and Information Science
  30. ;                        University of Massachusetts
  31. ;                        Amherst, Massachusetts 01003
  32. ;
  33. ; This software was conceived, designed, and written by Dan Suthers 
  34. ; while supported by the National Science Foundation under grant number
  35. ; MDR 8751362, and by a fellowship from Apple Computer, Inc., Cupertino,
  36. ; CA.  Partial support was also received from the Office of Naval Research
  37. ; under a University Research Initiative Grant, contract N00014-86-K-0764.
  38. ; I wish to acknowledge the generous support of Beverly Woolf, who obtained 
  39. ; the above grants and encouraged me to pursue my own research interests in
  40. ; her lab.  This work would not have been possible without the resources and
  41. ; stimulating environment of the Computer and Information Science department.
  42. ;
  43. ; Permission to use, modify, and distribute this software is granted subject 
  44. ; to the following restrictions and understandings:
  45. ; 1. The file header, including this notice, shall be retained, and may be
  46. ;    extended to include documentation of modifications to the software.
  47. ; 2. This material is for nonprofit educational and research purposes only.
  48. ;    Users are requested, but not required, to inform Mr. Suthers of any 
  49. ;    noteworthy uses of this software.
  50. ; 3. Mr. Suthers and the University of Massachusetts make no warrantee or
  51. ;    representation that the operation of this software will be error free,
  52. ;    and are under no obligation to provide any services.
  53. ; 4. Any user of such software agrees to indemnify and hold harmless Mr.
  54. ;    Suthers and the University of Massachusetts from all claims arising 
  55. ;    out of the use or misuse of this software, or arising out of any 
  56. ;    accident, injury, or damage whatsoever, and from all costs, counsel
  57. ;    fees, and liabilities incurred in or about any such claim, action, or
  58. ;    proceeding brought thereon.
  59. ; 5. All materials and reports developed as a consequence of the use of 
  60. ;    this software shall duly acknowledge such use, in accordance with
  61. ;    the usual standards of acknowledging credit in academic research.
  62. ;
  63. ; Status:       Mostly done.
  64. ;
  65. ; Changes:      See other files.
  66. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  67. ;;;
  68. ;;;                         USER'S DOCUMENTATION
  69. ;;;
  70. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  71. #|
  72.  
  73. The most up-to-date documentation is to be found associated with the 
  74. definitions of the relevant SM object types (RULE and TRACE-NODE :comments);
  75. and associated with the function definitions.  Here I provide some examples 
  76. and guidelines.
  77.  
  78. Variables in rules are the same as DNET variables, namely, symbols in the ? 
  79. package. They are declared with (dnet:defvariable <name>). Special forms 
  80. recognized in the rules include:
  81.  
  82. (:AND <C1> ... <Cn>) - If in the consequent, this is only for convienence, 
  83.   and the rule is parsed into multiple rules.  If in the antecedent, forward
  84.   and backward chaining tries to process each of <C1> ... <Cn> in the order 
  85.   given. Any :AND not at top level in the consequent results in factoring of 
  86.   the consequent.  This is logical conjunction.  SUPPORT doesn't short-circuit 
  87.   evaluation when :record-failure is T.  This allows applications to identify 
  88.   "near misses", etc.
  89.  
  90. (:SEQ <C1> .. <Cn>) - Like :AND, but is always short-circuited on failure,
  91.   even when :record-failure is T.  (SEQuential conjunction.)
  92.  
  93. (:OR <f1> ... <fn>) - for convienence in the antecedent only. The rule will 
  94.   be stored as multiple rules resulting from factoring out :OR.  Ignored in
  95.   the consequent.
  96.  
  97. (:LISP <expr1> ... <exprN>) - Variables in the current bindings list are
  98.   lambda-bound, and the expressions are evaluated, as in PROGV, the last
  99.   value being returned.  When the :LISP form occurs in the antecedent, 
  100.   the result of evaluation is used to determine success (whether forward
  101.   or back chaining).  It is useful in the consequent when side effects 
  102.   are desired in forward chaining, and to insert the results of lisp 
  103.   evaluation in the consequent expression.  To enable both uses, the
  104.   result of a :LISP at top level is ignored (not treated as a derived
  105.   expression to be added to the data dnet); while the result of a :lisp
  106.   :lisp embedded in an expression is included in that expression when
  107.   it is indexed as a new datum.
  108.  
  109. (:BIND <var> <expr>) - Variables in <expr> are bound as for :LISP, <expr>
  110.   is evaluated, and the result is bound to <var>, which must be a variable. 
  111.   Useful in antecedent to define variables used in consequent, eg. to prevent
  112.   use of :lisp in consequent which needs to be matched to for backchaining. 
  113.   (An example of this will be given, see "OHMS-LAW" and "OHMS-LAWLESS".)
  114.  
  115. (:DELETE <expr>) - Variables in <expr> are bound, and the expression is
  116.   deleted from the active data base.  (Any truth maintenance activities
  117.   will be up to the application, perhaps using the DELEXPR-HOOK of the DNET.)  
  118.   Applies only to forward rules.
  119.  
  120. The slots of an SM rule object are as follows:
  121.  
  122. ANTECEDENT:  A list, with :AND, :SEQ, :LISP and :BIND, possibly nested where
  123.              appropriate.  Factors after factoring :OR must be lists.
  124. CONSEQUENT:  A list, :AND and :SEQ may be embedded, :LISP OK but :BIND and 
  125.              :OR not interpreted.  
  126. DIRECTIONS:  One of :FORWARD :FORWARD-UNIQUE, :BACKWARD, or :BOTH: the way the
  127.              rule is used. See :comments on the slot for details.
  128. INTERNED-IN: A list of DNETs the rule is stored in.  ADD-RULE updates it. 
  129. INFO:        Association list of keywords to info needed by your code.
  130. COMMENTS:    String -- intent of the rule, etc.
  131.  
  132. Now for examples. The simplest kind of rule has no logical combiners or 
  133. escapes to lisp, and can be used both forwards and backwards to deduce new
  134. data.  These rules are essentially subclass statements:
  135.  
  136. (defvariable x)
  137. (rule sad-fact
  138.       :antecedent (human ?:x)
  139.       :consequent (mortal ?:x))
  140.  
  141. This next rule illustrates several things ...
  142.  
  143. (defvariable r)
  144. (rule run-away
  145.       :antecedent (:and (in (room ?:r) self) (burning (room ?:r)))
  146.       :consequent (:lisp (run-like-hell-from-room ?:r))
  147.       :directions :forward)
  148.  
  149. 1. During forward reasoning, :AND is interpreted specially (not matched) 
  150.    by seeing if both the enclosed forms match some datum in the DB with 
  151.    consistent variable bindings.
  152. 2. When :LISP is seen, the interpeter lambda-binds ?:r, then calls EVAL.
  153. 3. The rule need only be stored in the forward form, as we never 
  154.    backchain on :LISP consequents.
  155. 4. Notice I have ROOM wrapped around most occurances of ?:r. While working
  156.    out some detailed examples, I found that sometimes I wanted rules with 
  157.    the same antecedent to apply to different TYPES of objects, yielding 
  158.    different results according to the type.  For example, you may use a 
  159.    different procedure to escape a burning ship.  One solution is to require
  160.    that all references to variables, in both rules and data, are enclosed
  161.    in a predication of the type the variable is restricted to.  (I don't do
  162.    this for :LISP because the evaluator doesn't need to mess with it.) The
  163.    drawback is if a rule applies to multiple types, you have to generate a
  164.    different one for each type (perhaps automatically, by inheritance).
  165.  
  166. To illustrate factoring, the following rule is parsed into six rules:
  167.  
  168. (rule says-a-lot
  169.       :antecedent (:or (whale ?:x) (man ?:x))
  170.       :consequent (:and (mortal ?:x) (mammal ?:x)))
  171.  
  172. Internally to DNET, it is stored as these forward rules:
  173.   (whale ?:x) => (mortal ?:x)
  174.   (whale ?:x) => (mammal ?:x)
  175.   (man ?:x)   => (mortal ?:x)
  176.   (man ?:x)   => (mammal ?:x)
  177. and these backwards rules:
  178.   (mortal ?:x) => (:or (whale ?:x) (man ?:x))
  179.   (mammal ?:x) => (:or (whale ?:x) (man ?:x))
  180. If you read the Programmer's Introduction, you'll find out why this involves
  181. only 4 indexed expressions, and how the expressions on the right are stored
  182. in the EXPR-INFO of these indexed expressions.  The antecedents for back-
  183. chaining rules are not parsed for efficiency reasons (the order of components
  184. of :AND, :SEQ, and :OR contains control information about what to try first,
  185. and factoring produces redundant expressions, forcing the backchainer to repeat 
  186. work previously done).
  187.  
  188. To summarize discussion in the programmer's introduction on the effect of using 
  189. :AND and :OR in the antecedent and consequent on forward and backward rules:
  190.  
  191. 1. There is NO DIFFERENCE in logical behavior between using :AND (or :SEQ) in 
  192.    the CONSEQUENT of a single rule and writing two rules, whether reasoning 
  193.    FORWARD OR BACK.
  194. 2. There is NO DIFFERENCE to FORWARD chaining whether you write one :OR 
  195.    ANTECEDENT or two rules.
  196. 3. There is NO DIFFERENCE to BACKWARD chaining whether you write one :OR 
  197.    ANTECEDENT or two rules.
  198. 4. There is IS A DIFFERENCE in using :AND in the ANTECEDENT, since some 
  199.    rules are not expressible without it.  Forward chaining cannot match 
  200.    to an :AND antecedent without special interpretation to see if the conjunct 
  201.    holds.  For this reason, there are two forward chaining functions.  One
  202.    interpets :AND over a data base, the other triggers off a single datum.
  203. 5. :OR in the CONSEQUENT isn't allowed (well, you can do it but the code
  204.    ignores it).  We restrict the expressiveness of the language in this way
  205.    for tractability reasons.  (:NOT is not used for similar reasons.)
  206. 6. Exceptions to the above are as follows:
  207.    a. When analyzing SUPPORT trees, the form of the tree will differ depending
  208.      on the above choices, though the logical support is the same.  Also, :AND 
  209.      permits :record-failure to try conjuncts subsequent to a failing one, 
  210.      while :SEQ does not.
  211.    b. If there are :LISP side effects, the difference in rule order versus clause
  212.      order may result in a difference in side effects in #'s 1, 2 and 3.
  213.    c. Nested :ANDs and :ORs in the antecedent of backwards rules may be more
  214.       efficient than the separate rule representation, though logically
  215.       equivalent, because factoring results in redundant expressions which
  216.       causes the backchainer to do redundant processing.
  217.  
  218. Here is an example of :BIND, and how it is better than :LISP in some cases.
  219. Note also the use of the (<type> <variable>) technique to restrict the 
  220. applicability of a rule.
  221.  
  222. (defvariable r1)
  223. (defvariable p1)
  224. (defvariable p2)
  225. (defvariable v)
  226. (defvariable a)
  227.  
  228. (rule ohms-law
  229.       :antecedent (:and (ports (resistor ?:r1) (port ?:p1) (port ?:p2))
  230.                         (resistance (resistor ?:r1) (ohms ?:r))
  231.                         (voltage-across (port ?:p1) (port ?:p2) (volts ?:v))
  232.                         (:bind ?:a (/ ?:r ?:v)))
  233.       :consequent (current-at (port ?:p2) (amps ?:a)))
  234.  
  235. :BIND makes it possible to use this rule in both directions, because it 
  236. defines a variable in the antecedent so it can be used in the consequent 
  237. without a messy :LISP.  If I had written:
  238.  
  239. (rule ohms-lawless
  240.       :antecedent (:and (ports (resistor ?:r1) (port ?:p1) (port ?:p2))
  241.                         (resistance (resistor ?:r1) (ohms ?:r))
  242.                         (voltage-across (port ?:p1) (port ?:p2) (volts ?:v)))
  243.       :consequent (current-at (port ?:p2) (amps (:lisp ?:a (/ ?:r ?:v)))))
  244.  
  245. the consequent would contain an ugly :LISP thing that cannot be matched to
  246. in back chaining.  The backchainer will know how to deal with :BIND in an 
  247. antecedent, since it interprets each of the conjuncts one by one, and does
  248. not attempt to match to :BIND.
  249.  
  250. DNET and RULEs:  The code is written so that you may save a DNET of rules,
  251. reload it in a new session without loading the SM RULE objects stored in
  252. it, and things will still work.  This saves space when running versions of
  253. the system where you don't expect to edit the rules.  The code for defining 
  254. RULEs and loading them into a DNET is separated from the inferencing code.  
  255. Both share a data definitions file.  This allows separate loading, saving 
  256. more space.
  257.  
  258. See also the Programmer's Introduction for an idea of how rules are stored
  259. and processed.
  260.  
  261. ---------------------------------------------------------------------------
  262. SUPPORT TREES
  263.  
  264. The SUPPORT function takes a pattern as argument, and returns a tree of
  265. all ways in which back chaining supports the pattern.  It has a 
  266. :RECORD-FAILURE &key arg, which if T forces it to record failures as well 
  267. as successes. Otherwise NIL is returned on failure.
  268.  
  269. The TRJ-NODE structure has CLAIM, MODALITY, & SUPPORT slots.
  270.  
  271. * CLAIM is a pattern (may have variables), or an :AND or :OR expression.
  272.  
  273. * MODALITY says whether the CLAIM is supported, and with what modality.
  274.     (Right now we are using just "true" and "false", which I'll represent
  275.     as :SUPPORTED and :UNSUPPORTED.)  We need this slot because a non-NIL 
  276.     SUPPORT does not necessarily mean failure when :RECORD-FAILURE is T.
  277.  
  278. * SUPPORT is a list of TRJ-ARC structures.  If NIL, this indicates failure 
  279.     to support the CLAIM, but if non-NIL and :RECORD-FAILURE is T, you have
  280.     to look at MODALITY to tell.  This list is an implicit disjunction: each 
  281.     TRJ-ARC points to a subtree which is an alternate way to support the goal.
  282.  
  283. A TRJ-ARC consists of WARRANT, GROUNDS, and BINDINGS.
  284.  
  285. * WARRANT is one of :ASSERTED, :AND, :SEQ, :OR, or the name of a rule.
  286.     :ASSERTED means the CLAIM is supported by being found in the data base;
  287.     :AND, :SEQ, and :OR mean this support structure decomposes the indicated
  288.     operator. <Rule> means the arc is sanctioned by the indicated rule.
  289.  
  290. * GROUNDS and BINDINGS are as follows:
  291.  
  292.   - CLAIM of (:AND <C1> ... <Cn>) gets TRJ-ARCs of form:
  293.  
  294.     #S(TRJ-ARC WARRANT :AND 
  295.                GROUNDS (<support-for-C1> ... <support-for-Cn>) 
  296.                BINDINGS <bindings>)
  297.  
  298.     where <support-for-Ci> are TRJ-NODEs, and in a given list each 
  299.     <support-for-Ci> unifies with its corresponding <Ci> under the same 
  300.     <bindings>.  Since the bindings must be consistent, yet there may be
  301.     multiple ways for them to be consistent, an :AND may produce multiple
  302.     arcs.
  303.  
  304.   - CLAIM of (:SEQ <C1> ... <Cn>) gets TRJ-ARCs similar to :AND, of form:
  305.  
  306.     #S(TRJ-ARC WARRANT :SEQ
  307.                GROUNDS (<support-for-C1> ... <support-for-Ck>) 
  308.                BINDINGS <bindings>)
  309.  
  310.     where <support-for-Ck> is for the last conjunct that succeeded (k=n if
  311.     :record-failure nil, since otherwise the arc is not recorded). 
  312.  
  313.   - CLAIM of (:OR <D1> ... <Dn>)  gets TRJ-ARCs of form:
  314.  
  315.     #S(TRJ-ARC WARRANT :OR
  316.                GROUNDS (<support-for-D1> ... <support-for-Dn>)
  317.                BINDINGS (<bindings1> ... <bindingsn>))
  318.  
  319.     If record-failure is NIL, only successful Di are on the list.  The bindings
  320.     list corresponds to the grounds list.  Since bindings do not have to be
  321.     coordinated across disjuncts, there is only one TRJ-ARC for an :OR node.
  322.     Why record this at all?  It seems like a NO-OP, and we could lift the
  323.     disjunction in GROUNDS up into the implicitly disjunctive SUPPORT slot
  324.     of the TRJ-NODE that contains this.  My guess is that we want to be able
  325.     to tell what disjunctions are due to alternate ways to match, and what
  326.     due to a specified :OR, because it will affect how we explain and justify
  327.     the results.  When someone writes :OR, they are saying something about the
  328.     semantics of the concept, not just that the concept may apply to multiple
  329.     entities (i.e. not just that a variable binds to multiple objects).
  330.  
  331.   - CLAIM of <pattern> gets TRJ-ARCs of one of these forms:
  332.  
  333.     #S(TRJ-ARC WARRANT <rule-name> 
  334.                GROUNDS <support-node>
  335.                BINDINGS <bindings>)
  336.     where <bindings> unifies <pattern> with the consequent of <rule-name>; @@@ UNIFIES?
  337.  
  338.     #S(TRJ-ARC WARRANT :ASSERTED
  339.                GROUNDS <expression>
  340.                BINDINGS <bindings>)
  341.     where <bindings> unifies <pattern> with <expression> (which has no variables).
  342.  
  343.   - :LISP and :BIND are recorded by ... ?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  344.  
  345. WHEN :RECORD-FAILURE is T:
  346.  
  347. We record unsupported subgoals so analysis may be done concerning why a CLAIM may 
  348. be UNsupported.  There are some decisions to make about what to record.  If a 
  349. subgoal is supported, do I also record ways in which it failed to be supported?
  350. We decided not to do this.  In general, we are avoiding any record of failure
  351. which requires that the pattern matching facilities of DNET be reimplemented to
  352. keep track of failure.  That matching is automatic, and we won't record failure
  353. at that granularity.  Furthermore, to do so would make everything in a database
  354. a potential failure.  We want in principle to record only the failure of those
  355. subgoals having some conceptually useful granularity.
  356.  
  357. However there is still some decision to be made here.  Suppose the top level 
  358. CLAIM fails.  Do I always return:
  359.   #S(TRJ-NODE CLAIM <goal> MODALITY :UNSUPPORTED SUPPORT NIL)?
  360. This doesn't tell us any thing; might as well return NIL.  So how can we give 
  361. more info without having to pick apart how the pattern matcher failed?  
  362.  
  363. - :AND and :OR goals are always decomposed, even if they failed.  Their
  364.   recursive <trj-node>s tell us something about what happened.  
  365.  
  366. - :SEQ stops at the first failure (it was implemented for this purpose).
  367.  
  368. - Patterns that a rule matched to have a subtree for the rule to show how the
  369.   antecedent failed.
  370.  
  371. - Patterns that failed without a rule match are the terminals.  They look like:
  372.     #S(TRJ-NODE CLAIM <pattern> SUPPORT NIL)
  373.  
  374. Importantly, we RETAIN ANY SUCCESSFUL SUBGOAL TREES; otherwise this entire tree 
  375. would only be telling us how the :ANDs and :ORs are nested (which we already know),
  376. and what rules failed.  We want to also know how close the rules got.
  377.  
  378.  
  379. |#
  380.  
  381. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  382. ;;;                       PROGRAMMER'S INTRODUCTION
  383. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  384. #|
  385.  
  386. This section motivates how DNET is used to support rules.  The actual code
  387. is more elaborate than the examples below, but this will give you an initial
  388. idea of the approach, to enable you to read the actual code more easily.
  389. If you have loaded DNET, you can evaluate the expressions given as examples
  390. to see the examples run.
  391.  
  392. By way of review of what the documentation in the DNET package will tell you:
  393. - DNET can store any expression consisting of UN-DOTTED lists and atoms.
  394. - A variable is a symbol in the ? package. Create them with: (defvariable x).
  395. - Store an expression with: (indexpr <expr> <dnet>).
  396. - Associate information with it with: (expr-info <expr> <dnet>).
  397. - Retrieve expressions matching a pattern with (match-pattern <pat> <dnet>).
  398. - Retrieve patterns matching an expression with: (match-expression <expr> <dnet>).
  399. - Retrieve patterns matching a pattern with: (match <pat> <dnet>).
  400. - We make a rule-base dnet with (make-dnet <name-of-rule-base>).
  401.  
  402. ---------------------------------------------------------------------------
  403. INDEXING OF RULES:
  404.  
  405. See the documentation of the SM definition of a RULE (in this file) for the 
  406. current syntax of a rule.  SM RULE objects are "loaded" into a rule-base by
  407. indexing into the DNET which constitutes that rule base. The approach taken 
  408. stores forward and backward forms of the rules separately. This lets you 
  409. control which way a rule is to be used, and allows the use of far more 
  410. efficient algorithms for matching.  Rules are also parsed before storing to 
  411. factor out any :OR's in the antecedent or :AND's in the consequent.  (:SEQ 
  412. is also factored in consequents, since the order is preserved.)
  413.  
  414. The forward form is stored by indexing into the DNET with the antecedent, and
  415. storing in its INFO a list of "rule records" containing the consequents reached 
  416. by that antecedent.  The backwards form does the reverse.  Antecedents and 
  417. consequents indexed in the DNET are distinguished from each other by consing 
  418. the keys :ANTECEDENT and :CONSEQUENT onto them, respectively.  This ensures that
  419. an antecedent doesn't get retrieved when we are matching to find a consequent, 
  420. and vice versa.  Rule records in the INFO lists are tagged by the rule that 
  421. placed them there.  This lets us tag each deduction with the responsible rule, 
  422. and to merge redundant parts of rules without loosing the ability to delete only 
  423. one of them later on.
  424.  
  425. Again, if you have DNET loaded, you may evaluate the uncommented forms as we go.
  426. To get things started, make a data and a rules DNET, and define a variable:
  427.  
  428. (in-package :RULE)
  429. (make-dnet :data)
  430. (make-dnet :rules)
  431. (defvariable x)
  432.  
  433. Our (bogus) example rules will be:
  434.  
  435. (rule r1
  436.   :antecedent (:and (professional ?:x) (works-at-home ?:x))
  437.   :consequent (applicable home-office-deduction ?:x))
  438.  
  439. (rule r2 
  440.   :antecedent (:or (doctor ?:x) (lawyer ?:x))
  441.   :consequent (professional ?:x))
  442.  
  443. Factoring has no real effect on R1. Its forward form is stored by code that
  444. has essentially the following effect:
  445.  
  446. (indexpr (cons :antecedent '(:and (professional ?:x) (works-at-home ?:x)))
  447.          :rules
  448.          (list (cons 'r1 '(applicable home-office-deduction ?:x))))
  449.  
  450. The backwards form is stored by:
  451.  
  452. (indexpr (cons :consequent '(applicable home-office-deduction ?:x))
  453.          :rules
  454.          (list (cons 'r1 '(:and (professional ?:x) (works-at-home ?:x)))))
  455.  
  456. Factoring of R2 yields an implicit disjunction of antecedents:
  457.  
  458. ; ((doctor ?:x) (lawyer ?:x))
  459.  
  460. Factoring is not always this trivial: it can factor out embedded :OR's too.
  461. For example, an antecedent of (:and (bar ?:x) (:or (foo ?:x) (fie ?:x))) 
  462. factors to:
  463.  
  464. ; ((:and (bar ?:x) (foo ?:x)) (:and (bar ?:x) (fie ?:x))).
  465.  
  466. Now R2 is stored by:
  467.  
  468. (indexpr (cons :antecedent '(doctor ?:x))
  469.          :rules
  470.          (list (cons 'r2 '(professional ?:x))))
  471. (indexpr (cons :antecedent '(lawyer ?:x))
  472.          :rules
  473.          (list (cons 'r2 '(professional ?:x))))
  474. (indexpr (cons :consequent '(professional ?:x))
  475.          :rules
  476.          (list (cons 'r2 '(:or (doctor ?:x) (lawyer ?:x)))))
  477.  
  478. Note we associated the consequent to the unparsed form of the antecedent.
  479. This is because the :ORs of an antecedent may contain control information,
  480. and it was determined that parsing forces backchaining to do redundant 
  481. processing.  Now, look at what is indexed in your rules DNET with:
  482.  
  483. (mapcar #'(lambda (e) (format t "~%~S => ~S" e (expr-info e :rules)))
  484.         (all-expressions :rules))
  485.  
  486. ---------------------------------------------------------------------------
  487. FORWARD REASONING:
  488.  
  489. There are two kinds of forward reasoning supported by separate functions. 
  490. In one, INFER-FROM-DATUM, we see if any rules trigger from a single datum in 
  491. isolation.  In the other, FORWARD-CHAIN, we provide a DNET of data and a
  492. DNET of rules, and allow forward reasoning to run on any rules which match 
  493. any combination of data.  An argument controls whether to run once or run
  494. until quiescence (no new data added).
  495.  
  496. To illustrate INFER-FROM-DATUM triggering off of an isolated new datum, 
  497. assume we have just found out that (doctor joe):
  498.  
  499. (indexpr '(doctor joe) :data)
  500.  
  501. and we want to make whatever immediate inferences are possible.  In 
  502. effect, we find the applicable forward rules as follows:
  503.  
  504. (match-expression (cons :antecedent '(doctor joe))
  505.                   :rules)
  506.  
  507. ==> ((:ANTECEDENT DOCTOR ?:X))
  508.     (((?:X . JOE)))
  509.  
  510. Two values are returned: a list of rule antecedents matching the new datum, 
  511. and a list of binding sets corresponding to the list of rules. Since the
  512. results are non-NIL we know we have at least one rule.  For each rule in
  513. the first list (there is only one), we have to retrieve its EXPR-INFO, 
  514. substitute the bindings specified in the second list, yielding the rule's 
  515. conclusion.  Let's look at that in two parts:
  516.  
  517. (expr-info '(:antecedent doctor ?:x) :rules)
  518.  
  519. ==> ((R2 PROFESSIONAL ?:X))
  520.  
  521. We get back a list of all conclusions which the antecedent sanctions.
  522. Here we have only one, labeled by the rule which provided it.  If there
  523. were more than one, they could have come from an :AND in the consequent
  524. of the rule, and/or from other rules with the same antecedent.
  525.  
  526. To process all the conclusions, we map across the expr-info, taking 
  527. the CDR to knock off the rule names, and substitute for the variable
  528. (the actual code uses a RULE-RECORD abstraction instead of CDR, etc.):
  529.  
  530. (mapcar #'(lambda (c) (substitute-bindings '((?:x . joe)) (cdr c)))
  531.         (expr-info '(:antecedent doctor ?:X) :rules))
  532.  
  533. ==> ((PROFESSIONAL JOE))
  534.  
  535. The result is an implicit conjunction of new data.  FORWARD-CHAIN now
  536. INDEXPRs each into a database of data:
  537.  
  538. (indexpr '(professional joe) :data)
  539.  
  540. You can see the current contents of :data with:
  541.  
  542. (all-expressions :data)
  543.  
  544. The other kind of forward reasoning, FORWARD-CHAIN, tries to apply a rule
  545. base to an entire DNET of data.  Unlike INFER-FROM-DATUM, the chain version
  546. is capable of interpreting :AND in the antecedent.  This can only be done 
  547. when the entire data DNET is being operated on, because you have to try 
  548. matching to all combinations of data to see if an :AND succeeds, not just 
  549. an isolated added datum. FORWARD-CHAIN iterates over all the forward rules, 
  550. attempting to match each to the data. An internal form of MATCH-PATTERN is 
  551. used instead of MATCH-EXPRESSION, because we are matching a pattern to a 
  552. DNET of variable-less expressions.  If the antecedent being tested has no 
  553. :AND, the code is in effect:
  554.  
  555. (match-pattern (cdr '(:antecedent doctor ?:x)) :data)
  556.  
  557. ==> ((DOCTOR JOE))
  558.     (((?:X . JOE)))
  559.  
  560. This tells us that one datum matched to the rule, and tells us the bindings 
  561. we need to substitute into the rule's consequent to get the conclusion.  
  562. The remainder is similar to the above example.
  563.  
  564. If an :AND is seen, FORWARD-CHAIN applies the above process of matching 
  565. to the data to each conjunct in turn, aborting if any fails to produce a 
  566. result.  The bindings of the results must agree, so those data that match 
  567. but don't agree in bindings are eliminated.  Bindings used for matching each
  568. conjunct are substituted into subsequent conjuncts before proceeding.  If 
  569. multiple data match a conjunct, the :AND processing splits for each way of 
  570. matching, with different bindings substituted for each.  If a :LISP or
  571. :BIND form is encountered, it is processed as described in the User's 
  572. Introduction, by binding the variables and evaluating.
  573.  
  574. ----------------------------------------------------------------------------
  575. BACKWARD REASONING:
  576.  
  577. First let's reset the data base, so we can use the same example again:
  578.  
  579. (make-dnet :data)
  580.  
  581. Here we have a goal and want to base it in data.  The rule representation
  582. is similar to above, and won't be spelled out in detail here.  Matching is
  583. more complicated: I first describe backchaining as if goals never have
  584. variables, then deal with that problem.  
  585.  
  586. (indexpr '(works-at-home joe) :data)
  587. (indexpr  '(doctor joe) :data)
  588.  
  589. Say our goal is (applicable home-office-deduction joe). Since the goal has 
  590. no special operator, see if any rules conclude the goal:
  591.  
  592. (match-expression 
  593.  (cons :consequent '(applicable home-office-deduction joe))
  594.  :rules)
  595. ==> ((:CONSEQUENT APPLICABLE HOME-OFFICE-DEDUCTION ?:X))
  596.     (((?:X . JOE)))
  597.  
  598. The INFO associated with this is an implicitly disjunctive list of rule-records
  599. resulting from :OR-factoring the antecedents of all the rules that have this
  600. consequent.
  601.  
  602. (expr-info '(:consequent applicable home-office-deduction ?:x)
  603.             :rules)
  604. ==> ((R1 :AND (PROFESSIONAL ?:X) (WORKS-AT-HOME ?:X)))
  605.  
  606. Substituting variables into the single rule-record returned above gives the
  607. subgoal for backchaining:
  608.  
  609. (substitute-bindings
  610.  '((?:x . joe))
  611.  (rest (first (expr-info '(:consequent applicable home-office-deduction ?:x)
  612.                          :rules))))
  613. ==> (:AND (PROFESSIONAL JOE) (WORKS-AT-HOME JOE))
  614.  
  615. A backchainer which recognizes :AND will know to work on the two goals 
  616.         (professional joe)  and  (works-at-home joe) 
  617. separately, trying to achieve both.  It would first check the :data data 
  618. base to see if they are present.  If not, it would apply the present process 
  619. recursively, trying to find a rule consequent that matches the subgoal.  For 
  620. example:
  621.  
  622. (match-pattern '(works-at-home joe) :data)
  623. ==> ((WORKS-AT-HOME JOE))
  624.     (NIL)
  625.  
  626. But (professional joe) is not found, and requires subgoaling:
  627.  
  628. (match-pattern '(professional joe) :data)
  629. ==> NIL
  630.     NIL
  631.  
  632. Recall (rule (doctor ?:x) (professional ?:x)) is in :rules and (doctor joe)
  633. in :data.  Then it is just a reapplication of what you've seen:
  634.  
  635. (match-expression (cons :consequent '(professional joe))
  636.                   :rules)
  637. ==>((:CONSEQUENT PROFESSIONAL ?:X))
  638.    (((?:X . JOE)))
  639.  
  640. (substitute-bindings
  641.  '((?:x . joe))
  642.  (rest (first (expr-info '(:consequent professional ?:X)
  643.                          :rules))))
  644. ==> (:OR (DOCTOR JOE) (LAWYER JOE))
  645.  
  646. (match-pattern '(doctor joe) :data)
  647. ==> ((DOCTOR JOE))
  648.     (NIL)
  649.  
  650. ------------
  651. Elaboration:
  652.  
  653. This simple picture is complicated primarily by (a) the presence of variables
  654. in goals, (b) maintaining consistency of bindings; and (c) splitting for
  655. multiple ways to satisfy a goal. 
  656.  
  657. Variables in goals mean we have to use MATCH instead of MATCH-EXPRESSION when
  658. retrieving rules.  Bindings may work in both directions.  For example, first
  659. add a rule that uses two variables so we can show bindings working both ways:
  660.  
  661. (defvariable y)
  662. (defvariable j)
  663. (indexpr '(:consequent loves ?:x ?:y)
  664.          :rules
  665.          '((R-love :or (loves ?:y ?:x) (unhappy ?:x))))
  666.  
  667. Assume our goal is (loves Mary ?:j).  Then to find applicable rules:
  668.  
  669. (match '(:consequent loves Mary ?:j) :rules)
  670. ==> ((:CONSEQUENT LOVES ?:X ?:Y))
  671.     (((?:J . ?:Y)))
  672.     (((?:X . MARY)))
  673.  
  674. Since the main goal has a variable in it, part of the "answer" we expect
  675. from the backchainer is a binding for this variable.  The first set of
  676. bindings, ((?:j . ?:y)) tells us what this answer is.  Unfortunately the
  677. answer is not immediately available: we have to backchain to find an answer
  678. for ?:y to know the answer for ?:x.  Before backchaining, the second set of 
  679. bindings has to be substituted into the antecedent, to get the more specific 
  680. (appropriately bound) subgoal: 
  681.  
  682. (substitute-bindings
  683.  '((?:x . mary))
  684.  (rest (first (expr-info '(:consequent loves ?:x ?:y)
  685.                          :rules))))
  686. ==> (:OR (LOVES ?:Y MARY) (UNHAPPY MARY))
  687.  
  688. The "answer" returned by recursive processing on a subgoal will include
  689. binding of any variables in the subgoal.  To get the bindings for the main
  690. goal, we have to substitute bindings for the subgoal into the CDRs of 
  691. bindings for the goal.  This replaces bindings of variables to variables
  692. with a more useful binding.  For example, assume subgoaling on 
  693.   (loves ?:y mary) 
  694. returns a result of 
  695.   ((?:y . joe)).
  696. Then the answer is got by:
  697.  
  698. (substitute-bindings '((?:y . joe)) '((?:j . ?:y)))
  699. ==> ((?:J . JOE))
  700.  
  701. (The actual code only allows substitution into the CDR, to avoid accidental
  702. replacement of ?:j.)  The backchainer returns this binding set as the second
  703. value of its answer.  The first value is constructed by substituting this
  704. binding set into the original goal expression:
  705.  
  706. (substitute-bindings '((?:j . joe)) '(loves Mary ?:j))
  707. ==> (LOVES MARY JOE)
  708.  
  709. Consistency of bindings across conjuncts is maintained by substituting in
  710. the bindings which satisfy a conjunct into all subsequent conjuncts.  Then
  711. they become more specific subgoals which are implicitly consistent with 
  712. those already satisifed.  All processing works with sets of binding sets,
  713. representing alternate ways to support, rather than with a single binding
  714. set as in the examples.  If a goal matches a data base in multiple ways,
  715. then all subsequent processing which depends on use of these bindings
  716. (including iteration across conjuncts) splits for each binding set.
  717. The programmer is advised to read RETRIEVE before trying to understand
  718. SUPPORT.
  719.  
  720. ----------------------------------------------------------------------------
  721. PARTITIONED RULE SETS:
  722.  
  723. It is a simple matter to partition the rules, eg:
  724.  
  725. (make-dnet :control-rules)
  726. (make-dnet :legal-rules)
  727. (make-dnet :term-definition-rules)
  728. ...
  729.  
  730. Then the interpreter would access the appropriate dnet.  A future feature of
  731. DNET will be the ability to define child dnets, which inherit all expressions
  732. from their parents but add some own of their own.  This is surprizingly 
  733. straightforward, and enables hypothetical reasoning by extending data bases
  734. as well.
  735.  
  736. ----------------------------------------------------------------------------
  737. RATIONALE for STORING THE RULE TWICE:
  738.  
  739. The above approach is efficient, and allows you to specify whether a rule
  740. is forward, backward, or both.  However, perhaps you want to use the 
  741. save-dnet facility to create editable files which don't have the rules 
  742. represented two different ways.  There are some problems, though. We have 
  743. to account for the presence of the entire expression in constructing what 
  744. to match to, but the stored rule expression contains variables, and so 
  745. does the retrieval cue. This means we have to use MATCH, which does full 
  746. unification. It slows things down enough to make the more complex dual-
  747. direction implementation worthwhile. To allow us to edit the rules in
  748. their original form, I have defined them as SM objects.
  749.  
  750. ----------------------------------------------------------------------------
  751. RATIONALE for FACTORING AND INDEXING RULES:
  752.  
  753. While an :AND in the antecedent serves a real purpose, :AND in the consequent
  754. and :OR in the antecedent are only for convenience, in writing one rule for
  755. what would otherwise be multiple but similar rules.  That is:
  756. - :AND in the consequent means you can use the antecedent to conclude any of
  757.     the conjuncts in the consequent.  A separate rule could have been written
  758.     for each.
  759. - :OR in the antecedent means you can conclude the consequent if any of the 
  760.     antecedent conditions obtains.  A separate rule could have been written for
  761.     each antecedent conditions.
  762. - :SEQ cannot be factored, since it serves control purposes.
  763. The purpose of parsing is to split these composite rules into their components,
  764. so that the matcher does not have to deal with :AND and :OR.  We INDEXPR parsed
  765. antecedents and consequents, and map them to their counterpart. But what about 
  766. the counterpart mapped to?  Is it ever necessary to parse it too? To answer this 
  767. question, consider:
  768.  
  769. :FORWARD rules may contain :AND or :SEQ in the consequent.  When a forward rule 
  770.   fires, we add all the data derived.  Hence, to save parsing at run-time, we should
  771.   store the :AND-factored form of the consequent in the expr-info mapped to by the
  772.   antecedent.  Also, it is concievable that in a poorly coordinated rule base, a 
  773.   different rule may use the same :OR-factored antecedent but draw different 
  774.   conclusions.  We want to store the union of all :AND-factored conclusions drawn
  775.   by rules sharing the same :OR-factored antecedents.  The solution is to map each
  776.  :OR-factored antecedent to an implicit conjunction of :AND-factored consequents.
  777.  
  778. :BACKWARD rules may contain :AND or :OR in the antecedent. Factoring may produce 
  779.   inefficient backchaining.  For example:
  780.      (:and (foo x) (fie x) (:or (fum x) (bar x)))
  781.   => ((:and (foo x) (fie x) (fum x))
  782.       (:and (foo x) (fie x) (bar x)))
  783.   In the parsed form, the backchainer will try foo and fie twice each.  This 
  784.   suggests making the info a list of rule-records containting the rule name
  785.   and the unfactored antecedent.  If multiple rules have the same consequent, 
  786.   we store a rule record for each on this list, with implicit disjunction.
  787.  
  788. IN SUMMARY: 
  789.   INDEXPR'ed ANTECEDENTS are :or-factored, and map to an implicit conjunct list
  790.     of :and-factored consequents (each of which should not contain :or's, as 
  791.     they are not interpreted). All the conjuncts are asserted when forward 
  792.     chaining.
  793.   INDEXPR'ed CONSEQUENTS are :and-factored, and map to an implicit disjunct 
  794.     list of unfactored antecedents (each of which may contain :ands and :ors).
  795.     The backchainer succeeds if it can support any one of them.
  796.  
  797. ---------------------------------------------------------------------------
  798. BACKCHAINING SPECIFICS
  799.  
  800. The current implementation of SUPPORT finds ALL ways a goal can be supported,
  801. and returns a tree of TRACE-NODEs representing this support.  All calling 
  802. except a top level user function is done using trace-nodes, and all returned 
  803. results are trace-nodes.  In the future, one will be able to specify the 
  804. extent to which support should be sought (e.g. stop after first proven), and
  805. get back a tree of trace-nodes with unexplored alternate ways to support the 
  806. goal.  At the same time, the backchainer will be reimplemented to be able to 
  807. take one of these partial trees and complete it.
  808.  
  809. This greatly simplifies the implementation of :AND and :OR.  No backtracking
  810. is required, as we are simply searching to find all possible consistent bound
  811. support trees.  Both :AND and :OR invoke recursive processing on all their
  812. constituents (short-circuiting only :AND if :record-failure is nil), the 
  813. difference being that bindings must be consistent across :AND calls, and their 
  814. results are merged into one support subtree, whereas :OR bindings are in 
  815. distinct worlds, and consititute alternate subtrees.
  816.  
  817. For example, say we have a data base:
  818.   {(foo A), (fie B), (fum B)}
  819. and a goal:
  820.   (:AND (:OR (foo ?:x) (fie ?:x)) (fum ?:x))
  821.  
  822. If we did not explore all alternatives, here's how we run into the backtracking
  823. problem with short circuiting [the actual code calls and returns with trace-nodes]:
  824.   Goal:   (:AND (:OR (foo ?:x) (fie ?:x)) (fum ?:x)); Bindings: ()
  825.     Calls Goal: (:OR (foo ?:x) (fie ?:x)); Bindings ()
  826.       Calls (foo ?:x); Bindings ()
  827.       Returns Success with Bindings (((?:x . A)))
  828.     Returns Success with Bindings (((?:x . A)))
  829.     Calls Goal: (fum ?:x); Bindings: (((?:x . A)))
  830.     Returns Failure [since (fum B) yields inconsistent bindings]
  831. Now we have to backtrack to pursue the other :OR option.  We have to know
  832. that (fie ?:x) is the next thing to try -- to not restart at the beginning
  833. of the :OR.  The way to do this is to NOT call an :OR-processor anew with
  834. the fresh :OR expression -- we have to have kept track of where we were in
  835. the :OR, so when we backtrack we know this is a choice point with remaining
  836. options to explore.
  837.  
  838. The full search approach interprets the :OR by splitting into alternate 
  839. subtrees:
  840.   Goal:   (:AND (:OR (foo ?:x) (fie ?:x)) (fum ?:x)); Bindings: ()
  841.     Calls Goal: (:OR (foo ?:x) (fie ?:x)); Bindings ()
  842.       Calls (foo ?:x); Bindings ()
  843.       Returns Success with Bindings (((?:x . A)))
  844.       Calls (fie ?:x); Bindings () [since :OR]
  845.       Returns Success with Bindings (((?:x . B)))
  846.     Calls Goal: (fum ?:x); Bindings: (((?:x . A)) ((?:x . B))) 
  847.       [Two alternate sutrees are represented by two binding sets.]
  848.     Returns Success with Bindings (((?:x . B))) [Inconsistent set filtered]
  849.   Returns Success with Bindings (((?:x . B)))
  850.  
  851. ---------------------------------------------------------------------------
  852. THOUGHTS ON UNIQUE BINDINGS 
  853.  
  854. Backchaining can run into an infinite loop when the consequent of a rule
  855. matches one of its antecedents, or this condition obtains between a set
  856. of rule in a circular manner.  Example:
  857.  
  858. (RULE VOLTAGE-PROPAGATION
  859.       :ANTECEDENT (:and (voltage-across (port ?:p1) (port ?:p2) (volts ?:v))
  860.                         (connected (port ?:p1) (port ?:p3))
  861.                         (connected (port ?:p2) (port ?:p4)))
  862.       :CONSEQUENT (voltage-across (port ?:p3) (port ?:p4) (volts ?:v)))
  863.  
  864. (voltage-across (port ?:p3) (port ?:p4) (volts ?:v)) Matches
  865. (voltage-across (port ?:p1) (port ?:p2) (volts ?:v)), and the rule invokes
  866. an infinite loop of back chaining.  One solution is to keep track of what
  867. bindings are currently active for each rule, and disallow rebinding each
  868. rule with the same bindings during the backchaining call.  This is the
  869. solution taken here.  Variables are uniquified for each rule, so we only
  870. have to keep track of bindings for variables without indexing them by rule.
  871.  
  872. A PSEUDO-solution is to write such rules in a different order, hoping to use 
  873. the other conjuncts to constrain the bindings of the variables in such a way
  874. that the offending conjunct cannot unify with the consequent of the same rule.
  875. This does not always work because (1) two rules may play off of each other;
  876. or even (2) one rule, such as the above, may play off against itself by
  877. alternating bindings (the voltage is "propagated" back and forth between two
  878. sets of ports).  Both of these have occured in my empirical tests.
  879.  
  880. Forward rules have a related problem.   A :LISP consequent forward rule
  881. could fire more than once in a given forward chaining invocation, and the
  882. number of times is unpredictable (depends on how many times other rules
  883. are able to add new data to the data base).  The designation :FORWARD-UNIQUE
  884. or :BOTH-UNIQUE indicates that a rule can not be invoked forward on the same 
  885. bindings twice.  This is not a real problem for datum adding forward rules 
  886. because they are only allowed to add or justify a given datum once, but for 
  887. efficiency the user may still want to designate such rules as :forward-unique.
  888.  
  889. Note the differences: a :FORWARD-UNIQUE prohibition is specified by the user
  890. for specific rules, and is in effect across calls to the forward chainer 
  891. (until FORGET-BINDINGS is invoked); while the backchaining unique rebinding 
  892. requirement is automatically applied to all backchaining rules, and is
  893. in effect only for the duration of a backchaining session.
  894.  
  895. @@@ Problems etc.:
  896.  
  897.  Forward :LISP rules fire currently as long as datum rules are firing, but no more.
  898.  Question is whether we want lisp rules that keep going until THEY fail to fire.
  899.  This would be better than having the number of times a :lisp rule fires be
  900.  dependent on what other (expression adding) rules are present.
  901.  
  902.  Question: Do we want to be able to specify forward unique (a) across sessions,
  903.  (b) during a session?  Latter may be needed for :LISP rules which
  904.  are not to repeat during a session, but should run each session.
  905.  
  906. |#
  907. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  908.  
  909. (in-package :RULE)
  910.  
  911. ;;; Basics.
  912. (require :DIALOGUE)
  913. (require :SM      )
  914. (require :DNET    )
  915. (require :MISC    )
  916. (require :MAPPINGS)
  917.  
  918. ;;; These definitions are always required.
  919.  
  920. (require :Rule-Defs
  921.          #+:CCL "ccl;MODULES:RULES:rule-defs")
  922.  
  923. ;;; This is only needed when editing SM RULE objects and building rule
  924. ;;; dnets.  Already-constructed dnets may be loaded without the SM
  925. ;;; RULE type being defined.
  926.  
  927. (require :Rule-Build
  928.                   #+:CCL "ccl;MODULES:RULES:rule-build")
  929.  
  930. ;;; Graphing stuff etc. is loaded if in Allegro.
  931. #+:CCL (require :SMEDIT       )
  932. #+:CCL (require :DNET-Browser )
  933. #+:CCL (require :GRAPHER      )
  934. #+:CCL (require :Rule-Browser 
  935.                 #+:CCL "ccl;MODULES:RULES:rule-browser")
  936.  
  937. ;;; Forward and backward reasoning may be loaded separately.
  938. (require :Rule-Forward 
  939.          #+:CCL "ccl;MODULES:RULES:rule-forward")
  940. (require :Rule-Back    
  941.          #+:CCL "ccl;MODULES:RULES:rule-back")
  942.  
  943. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  944. (provide :RULES)
  945. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  946. ;;; the end.